package com.googlecode.lohr.parser.combinators.matcher;


import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;

import com.googlecode.lohr.parser.combinators.MatchError;
import com.googlecode.lohr.parser.combinators.Parsers;
import com.googlecode.lohr.parser.combinators.node.AbstractNode;
import com.googlecode.lohr.parser.combinators.node.SequenceNode;

public class SequenceMatcher extends Matcher
{
	
	
	private final Matcher[] matchers;
	
	

	public SequenceMatcher(Matcher[] matchers)
	{
		this.matchers = matchers;
	}
	
	

	@Override
	public void match(final MatchContext ctx, int start) 
	{
		if (matchers.length <= 0) {
			ctx.matchComplete(this, start, new MatchError("This sequence contains no elements", start));
			return;
		}
		
		
		final Matcher seqMatcher= this;
		final int seqStart= start;
		
		if (matchers.length <= 1) {
			ctx.doMatch(matchers[0], start, new MatchListener() {
				public void onMatchFound(AbstractNode match) {
					AbstractNode node = new SequenceNode(seqMatcher, seqStart, match.getEnd());
					ArrayList<AbstractNode> children= new ArrayList<AbstractNode>();
					children.add(match);
					node.setChildren(children);
					ctx.matchFound(seqMatcher, seqStart, node);
				}
				public void onMatchComplete(MatchError matchError) {
					ctx.matchComplete(seqMatcher, seqStart, matchError);
				}
			});
			return;
		}
		
		MatchListener accumulator= new MatchListener() {
			private boolean headMatchesComplete= false;
			private AtomicInteger taskCount= new AtomicInteger();
			private MatchError _bestError= null;
			
			
			@Override
			public void onMatchFound(final AbstractNode headNode) {
				taskCount.incrementAndGet();
				ctx.doMatch(Parsers.seq(Parsers.tail(matchers)), headNode.getEnd(), new MatchListener() {
					public void onMatchFound(AbstractNode tailNode) {
						AbstractNode node = new SequenceNode(seqMatcher, seqStart, tailNode.getEnd());
						ArrayList<AbstractNode> children= new ArrayList<AbstractNode>();
						children.add(headNode);
						children.addAll(tailNode.getChildren());
						node.setChildren(children);
						ctx.matchFound(seqMatcher, seqStart, node);
					}
					
					@Override
					public void onMatchComplete(MatchError matchError) {
						if (matchError != null && (_bestError == null || matchError.position < _bestError.position)) {
							_bestError= matchError;
						}
						boolean last= taskCount.decrementAndGet() <= 0;
						if (headMatchesComplete && last) {
							if (!ctx.hasMatches(seqMatcher, seqStart)) {
								assert _bestError != null;
								ctx.matchComplete(seqMatcher, seqStart, _bestError);
							}
						}
					}
				});
			}
			
			@Override
			public void onMatchComplete(MatchError matchError) {
				headMatchesComplete= true;
				
				// terminate if head matcher failed
				if (matchError != null) 
					ctx.matchComplete(seqMatcher, seqStart, matchError);
			}
		};
		
		ctx.doMatch(matchers[0], start, accumulator);
	}
}